package de.kuttenmann.jflake;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.Callable;


public class Task implements Callable<Vector3D[]> {
    public static final double EPSILON = 1e-12;
    private Tracer tracer;
    private Node scene;
    private int depth;
    private int rays = 0;
    private Vector3D eye;
    private Light[] lights = {
	    new Light(new Vector3D(-0.5, -0.65, 0.9).normalize(), new Vector3D(
		    255.0, 255.0, 0.0)),
	    new Light(new Vector3D(0.5, -0.65, 0.9).normalize(), new Vector3D(
		    0.0, 255.0, 255.0)),
	    new Light(new Vector3D(0.5, -0.65, 0.0).normalize(), new Vector3D(
		    255.0, 0.0, 255.0)) };

    private ArrayList<Vector3D> rayArray = new ArrayList<Vector3D>();

    public Task(Tracer tracer, Node scene, int depth) {
	/* Eye, looking into Z */
	this.eye = new Vector3D(0.0, 0.0, -5.0);
	this.scene = scene;
	this.depth = depth;
	this.tracer = tracer;
    }

    @Override
    public Vector3D[] call() throws Exception {
	// ErgebnisArray
	ArrayList<Vector3D> colors = new ArrayList<Vector3D>();
	// Rays generieren
	Iterator<Vector3D> it = rayArray.iterator();
	while (it.hasNext()) {
	    Vector3D viewPoint = it.next();
	    Ray ray = new Ray(eye, viewPoint.normalize());
	    colors.add(traceRay(ray, depth));
	}
	tracer.addRays(rays);
	/* return pixel */
	Vector3D[] pixel = colors.toArray(new Vector3D[colors.size()]);
	return pixel;
    }

    private Vector3D traceRay(final Ray ray, int depth) {
	if (depth < 0)
	    return Vector3D.ZERO;

	// trace primary
	Hit hit = scene.intersect(ray, Hit.NO_HIT, false);
	rays++;

	if (hit.getDistance() == Hit.INFINITY)
	    return Vector3D.ZERO;

	Vector3D view = ray.getDirection().scale(-1.0);
	Vector3D point = ray.getOrigin().add(
		ray.getDirection().scale(hit.getDistance())).add(
		hit.getNormal().scale(EPSILON));
	Vector3D normal = hit.getNormal();

	Vector3D diffuse = Vector3D.ZERO;
	for (int l = 0; l < lights.length; l++) {
	    // directional light source
	    Vector3D source = lights[l].getDirection().scale(-1.0);
	    Vector3D color = lights[l].getColor();
	    // shadowed diffuse term
	    Ray shadowRay = new Ray(point, source);
	    // trace shadow
	    Hit shadowHit = scene.intersect(shadowRay, Hit.NO_HIT, true);
	    rays++;
	    // if not shadowed, count color
	    if (shadowHit.getDistance() == Hit.INFINITY)
		diffuse = diffuse.add(color.scale(normal.dot(source)
			/ lights.length));
	    // mirrored term
	    Vector3D reflection = normal.scale(2 * normal.dot(view)).sub(view);
	    Ray mirrorRay = new Ray(point, reflection);
	    diffuse = diffuse.add(traceRay(mirrorRay, depth - 1).scale(0.25));
	    rays++;
	}

	return diffuse;
    }

    public void addViewPoint(Vector3D vp) {
	rayArray.add(vp);
    }
}
